package moa.learners;

import moa.MOAObject;
import moa.core.Measurement;
import moa.learners.prefix.AlgoPrefixSpan;
import moa.learners.prefix.SequenceDatabase;
import moa.options.FlagOption;
import moa.options.IntOption;
import weka.core.Instance;
import weka.core.Option;

import adams.data.statistics.StatCalc;
import adams.data.utils.SAXUtils;

public class SAXLearner extends AbstractLearner {
	
	  /** for serialization. */
	  private static final long serialVersionUID = -7011253199243036622L;
	  
	  /** number of gaussian bins. (labels) */
	  protected int m_bins = 5;

	  /** number of windows. */
	  protected int m_windows = 80;
	  
	  /** If true output nominal, false output numeric  .*/
	  protected boolean m_OutputNominal = false;
	  
	  /** breakpoints. */
	  protected double[] m_bps=null;
	  
	  /** prefix span variables . */
	  private SequenceDatabase sequenceDatabase;
	  private AlgoPrefixSpan algo;

	public IntOption binsOption = new IntOption("bins", 'g',
			"Number of gaussian bins (>1).", 5);
	
	public IntOption paaOption = new IntOption("PAA", 'p',
			"Number of windows for PAA (>1).", 5);
	
	public IntOption minSupportOption = new IntOption("minSupport", 's',
			"Minimum support threshold.", 50, 0,
			100);
    
	public FlagOption topCurveOption = new FlagOption("topCurve",
			't', "Cut the sequence on the top of the curve.");
	
	public FlagOption bottomCurveOption = new FlagOption("bottomCurve",
			'b', "Cut the sequence on the bottom of the curve.");
	
	  /** 
	   * Sets whether to output nominal or numeric values.
	   * 
	   * @param dontNormalize	if true the values are not normalized
	   */
	  public void setNominal(boolean nominal) {
	    m_OutputNominal = nominal;
	    //invalidate();
	  }
	  
	  /**
	   * Gets whether output numeric or nominal values.
	   * 
	   * @return		false if values get normalized
	   */
	  public boolean getNominal() {
	    return m_OutputNominal;
	  }
	  
	  /**
	   * Sets the nth point setting.
	   *
	   * @param value 	the nth point
	   */
	  public void setBins(int value) {
	    if (value > 0) {
	      m_bins = value;
	    }
	    else {
	      System.err.println(
		  "'n' must be larger than 0 (provided: " + value + ")!");
	    }
	  }
	  
	  /**
	   * Sets the nth point setting.
	   *
	   * @param value 	the nth point
	   */
	  public void setWindows(int value) {
	    if (value > 0) {
	      m_windows = value;
	    }
	    else {
	      System.err.println(
		  "'n' must be larger than 0 (provided: " + value + ")!");
	    }
	  }

	  /**
	   * Returns the nth point setting.
	   *
	   * @return 		the order
	   */
	  public int getBins() {
	    return m_bins;
	  }
	  
	  /**
	   * Returns the nth point setting.
	   *
	   * @return 		the order
	   */
	  public int getWindows() {
	    return m_windows;
	  }

	
	
	@Override
	public void getModelDescription(StringBuilder out, int indent) {
		System.out.println("Run Algorithm");
		algo.runAlgorithm(sequenceDatabase);
		algo.printStatistics(sequenceDatabase.size(),out);
	}

	@Override
	protected Measurement[] getModelMeasurementsImpl() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void resetLearningImpl() {
		setBins(binsOption.getValue());
		setWindows(paaOption.getValue());
	    m_bps=adams.data.utils.SAXUtils.calcBreakPoints(getBins());
		sequenceDatabase = new SequenceDatabase(); 
		algo = new AlgoPrefixSpan(0.01*minSupportOption.getValue()); 
	}

	  /**
	   * processes the given instance (may change the provided instance) and
	   * returns the modified version.
	   *
	   * @param instance    the instance to process
	   * @return            the modified data
	   * @throws Exception  in case the processing goes wrong
	   */
	public void trainOnInstanceImpl(Instance inst) {
	    Instance	result;
	    double[]	values;
	    boolean	hasClass;
	    int		count;
	    
	    double[] stats=getMeanStdev(inst);
	    //System.err.println("Instance mean="+stats[0]+", sd="+stats[1]);
	    
	    hasClass = (inst.classIndex() > -1);

	    double[] ivals=new double[inst.numAttributes()-1];
	    count = 0;
	    // We read all the attributes 
	    //System.out.println("num attributes to add " + inst.numAttributes());
	    for (int i=0;i<inst.numAttributes();i++){
	      if (i == inst.classIndex()) {
	    	  continue;
	      }
	      ivals[count]=inst.value(i);
	      count++;
	    }
	    
	   
	    double[] saxvalues=adams.data.utils.SAXUtils.toSAX(ivals, getWindows(), m_bps);
	    String seq = "";
	    if (bottomCurveOption.isSet())
	    {
		    seq = "";
		    // First method to split the sequence (cut 2 by 2)
		    for (int j=0;j<saxvalues.length;j++)
		    {	 
		    	if (seq == "")
		    		seq+=(int)saxvalues[j] + " ";
		    	else
		    	{
		    		if (j % 2==0)
		    			seq+= "-1 " + (int)saxvalues[j] + " ";
		    		else
		    			seq+=(int)saxvalues[j] + " ";
		    	}
		    }
		    seq += "-1";	    	
	    }

	    
	    // Second method : new transaction after each top of the curve of PAA values
	    if (topCurveOption.isSet())	  
	    {
		    seq = "";
		    int last_in =0;
		    boolean up = true;
		    for (int j=0;j<saxvalues.length;j++)
		    {	 
		    	if (j == 0)
		    	{	
		    		seq+=(int)saxvalues[0] + " ";
		    		last_in = (int)saxvalues[0];
		    	}	
		    	else
		    	{
		    		if ((int)saxvalues[j]<last_in)
		    		{	
		    			if (up==true)	
		    			{	
		    				seq+= "-1 " + (int)saxvalues[j] + " ";
		    				up = false;
		    			}	
		    			else
		    			{	
		    				seq+=(int)saxvalues[j] + " ";
		    			}	
		    		}	
		    		else
		    		{	
		    			if ((int)saxvalues[j] != last_in) // enable to give two times the same item
		    			{
			    			seq+=(int)saxvalues[j] + " ";
			    			up = true;
		    			}
		    		}	
		    		last_in = (int)saxvalues[j];
		    	}
		    }
		    seq += "-1";	    	    	
	    }


	    System.out.println(seq);
	    sequenceDatabase.addSequenceString(seq);

	}


	@Override
	public MOAObject getModel() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isRandomizable() {
		// TODO Auto-generated method stub
		return false;
	}
	
	
	  /**
	   * Return an array where the 1st value is the mean, and the 2nd the standard deviation of
	   * the attribute values.
	   * 
	   * @param in		instance
	   * @return		mean, stdev
	   */
	  protected double[] getMeanStdev(Instance in){
	    double[] ret = new double[2];
	    StatCalc st=new StatCalc();
	    for (int i=0;i<in.numAttributes();i++){
	      if (i == in.classIndex()){
		continue;
	      }
	      st.enter(in.value(i));
	    }
	    ret[0]=st.getMean();
	    ret[1]=st.getStandardDeviation();
	    return(ret);
	  }

}
